package lumis.portal.channel.template;

import lumis.portal.PortalException;
import lumis.portal.PortalStringResource;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.dao.DaoException;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.progress.ProgressConfig;
import lumis.portal.progress.ProgressConfig.Type;
import lumis.portal.transaction.ITransaction;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.IResource;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.w3c.dom.Document;

/**
 * Updates channels from channel templates.
 *
 *
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 * @since 5.6.0
 */
public class ChannelTemplateUpdateChannelsRunner implements Runnable
{
	private static ILogger logger = LoggerFactory.getLogger(ChannelTemplateUpdateChannelsRunner.class);

	final private String userId;

	final private Locale userLocale;
	final private List<String> channelIds;
	final private String processId;
	final private long startTime;
	private int overallProgressMax=1;
	private int overallProgressValue=0;
	private IResource resource;

	/**
	 * Updates channels from given channel templates.
	 *
	 * @param sessionConfig a session configuration
	 * @param processId a processId used between back-end and front-end to send and receive information.
	 * @param channelIds the channels to be updated
	 * @since 5.6.0
	 * @deprecated
	 */
	@Deprecated
	public ChannelTemplateUpdateChannelsRunner(SessionConfig sessionConfig, String processId, List<String> channelIds)
			throws PortalException
	{
		this(sessionConfig.getUserId(), sessionConfig.getLocale(), processId, channelIds);
	}

	/**
	 * Updates channels from given channel templates.
	 *
	 * @param userId the user identifier that is going to execute the action.
	 * @param userLocale the user locale.
	 * @param processId a processId used between back-end and front-end to send and receive information.
	 * @param channelIds the channels to be updated
	 * @since 6.0.0
	 */
	public ChannelTemplateUpdateChannelsRunner(String userId, Locale userLocale, String processId,
			List<String> channelIds)
	{
		this.userLocale = userLocale;
		this.userId = userId;
		this.channelIds = channelIds;
		this.processId = processId;
		this.startTime = new Date().getTime();
		this.overallProgressMax = channelIds.size();
		this.resource = new PortalStringResource();
		this.resource.addResourcePath("lumis/service/portalmanagement/channeltemplate/strings/strings");
	}

	public void run()
	{
		try
		{
			SessionConfig sessionConfig = ManagerFactory.getAuthenticationManager().impersonate(userId);
			final ITransaction transaction = PortalTransactionFactory.createTransaction();
			try
			{
				transaction.begin();
				// begin the execution
				updateChannelsStructureFromTemplate(sessionConfig, processId, channelIds, transaction);
				try
				{
					transaction.commit();
				}
				catch (DaoException e)
				{
					throw new PortalException("STR_ERROR_COMMITTING_OPERATION", e);
				}
			}
			finally
			{
				transaction.dispose();
				ManagerFactory.getAuthenticationManager().endImpersonation(sessionConfig);
			}
		}
		catch (Exception e)
		{
			logger.error("Error during channel update", e);
		}
	}

	/**
	 * @since 5.6.0
	 */
	private void updateChannelsStructureFromTemplate(SessionConfig sessionConfig, String processId, List<String> channelIds, ITransaction transaction) throws PortalException, UnexpectedException
	{
		try
		{
			sendProgressNotification("STR_INITIALIZING", Type.BEGIN, null);
			// Maps all channels templates an their channels
			HashMap<String, List<String>> channelTemplatesMapping = getChannelTemplatesMapping(sessionConfig, channelIds, transaction);

			ChannelTemplateManager channelTemplateManager = (ChannelTemplateManager)ManagerFactory.getChannelTemplateManager();
			Set<String> templateIds = channelTemplatesMapping.keySet();

			//send the initializing progress message
			if(templateIds.size() > 1)
			{
				String updateInfoMessage = "[lumis:localize]STR_STARTED_CHANNEL_TEMPLATE_UPDATE[/lumis:localize]<BR>" +
										   "[lumis:localize]STR_TEMPLATE_COUNT[/lumis:localize]: " + Integer.toString(templateIds.size())+"<BR>" +
										   "[lumis:localize]STR_CHANNELS_TO_UPDATE_COUNT[/lumis:localize]: " + Integer.toString(channelIds.size());
				sendProgressNotification(updateInfoMessage, Type.INFO, null);
			}

			for(String templateId:templateIds)
			{
				// get channels to be updated using current template
				List<String> templateChannelIds = channelTemplatesMapping.get(templateId);
				// send update template info.
				String currentTemplateName = ManagerFactory.getChannelManager().getPath(sessionConfig, templateId, transaction);
				String templateInfoMessage = "[lumis:localize]STR_CHANNEL_TEMPLATE[/lumis:localize]: "+currentTemplateName+"<BR>[lumis:localize]STR_CHANNELS_TO_BE_UPDATED_COUNT[/lumis:localize]: " + Integer.toString(templateChannelIds.size());
				sendProgressNotification(templateInfoMessage, Type.INFO, null);

				updateProgressBar(0, "STR_CHARGING_CHANNEL_TEMPLATE;"+currentTemplateName);
				// get templateDocument
				Document templateDocument = channelTemplateManager.getSerializedChannelDocument(sessionConfig, templateId, transaction);

				// end charging message
				updateProgressBar(0, "STR_CHANNEL_TEMPLATE_CHARGED;"+currentTemplateName);

				for(String channelId:templateChannelIds)
				{
					String currentChannelName = ManagerFactory.getChannelManager().getPath(sessionConfig, channelId, transaction);
					updateProgressBar(0, "STR_UPDATING_CHANNEL;"+currentChannelName);

					// Creates a new transaction to updates each channel
					ITransaction updateTransaction = PortalTransactionFactory.createTransaction();
					try
					{
						updateTransaction.begin();
						// update channel
						channelTemplateManager.updateChannelStructureFromTemplate(sessionConfig, channelId, templateId, templateDocument, updateTransaction);
						updateTransaction.commit();
						// send end ok update message
						updateProgressBar(1, "STR_CHANNEL_UPDATED;"+currentChannelName);
						sendProgressNotification("STR_CHANNEL_UPDATED;"+currentChannelName, Type.END_OK, null);
					}
					finally
					{
						updateTransaction.dispose();
					}
				}
				// update finish successfully
				sendProgressFinalNotification("STR_UPDATE_FINISHED_SUCCESSFULLY", Type.END_OK);
			}
		}
		catch (PortalException e)
		{
			sendProgressFinalNotification(e.getLocalizedMessage(), Type.END_ERROR);
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	/**
	 * @since 5.6.0
	 */
	private HashMap<String, List<String>> getChannelTemplatesMapping(SessionConfig sessionConfig, List<String> channelIds, ITransaction transaction) throws ManagerException, PortalException
	{
		HashMap<String, List<String>> templateMapping = new HashMap<String, List<String>>();
		for(String channelId:channelIds)
		{
			//get channelConfig
			ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(sessionConfig, channelId, transaction);
			// get channel's parent template
			String parentTemplateId = channelConfig.getParentTemplateId();
			// get channelIds for this template
			List<String> mappedChannelIds = templateMapping.get(parentTemplateId);
			if(mappedChannelIds == null)
			{
				mappedChannelIds = new ArrayList<String>();
			}
			mappedChannelIds.add(channelId);
			// updates the mapping
			templateMapping.put(parentTemplateId, mappedChannelIds);
		}

		return templateMapping;
	}

	/**
	 * @since 5.6.0
	 */
	private void updateProgress(ProgressConfig progressConfig) throws PortalException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();

			if (!progressConfig.isLastMessage() && ManagerFactoryInternal.getProgressManager().getAbortedByProcessId(progressConfig.getProcessId()))
			{
				String message = "STR_OPERATION_CANCELED_BY_USER";
				throw new ManagerException(message);
			}

			String message = progressConfig.getMessage();
			if (message != null)
				message = ManagerFactory.getLocalizationManager().localize(message, resource, userLocale);
			progressConfig.setMessage(message);
			progressConfig.setElapsedTime((int)(new Date(). getTime() - this.startTime));
			ManagerFactoryInternal.getProgressManager().addOrUpdate(progressConfig);

			transaction.commit();
		}
		finally
		{
			transaction.dispose();
		}
	}

	/**
	 * @since 5.6.0
	 */
	private void updateProgressBar(int incrementOverallProgress, String itemName) throws PortalException
	{
		this.overallProgressValue += incrementOverallProgress;
		updateProgress(new ProgressConfig(processId, overallProgressMax, overallProgressValue, 0, 0, itemName, Type.STEP, false));
	}

	/**
	 * @since 5.6.0
	 */
	private void sendProgressNotification(String message, Type type, String innerProgressId) throws PortalException
	{
		if (type.equals(Type.STEP))
			throw new InvalidParameterException("Type.STEP is invalid. Use another type.");
		ProgressConfig progressConfig = new ProgressConfig(processId, overallProgressMax, overallProgressValue, 0, 0, message, type, false);
		progressConfig.setInnerProgressId(innerProgressId);
		updateProgress(progressConfig);
	}

	/**
	 * @since 5.6.0
	 */
	private void sendProgressFinalNotification(String message, Type type) throws PortalException
	{
		updateProgress(new ProgressConfig(processId, 0, 0, 0, 0, message, type, true));
	}

}
